/** 
  Copyright (c) 2008, 2009 Claudio Imbrenda
  This program is released under the terms of the
  GNU GPL version 3 or later.
*/
package macchina;

import java.awt.event.*;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.script.*;
import java.io.*;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaFileObject;



/** 
 * Macchina Ridotta. Implements the main emulation engine, with the
 * Javas-customizable instruction set.
 * @author Claudio Imbrenda
 */
public class Macchina implements Runnable{
  
 // approximates PHI, the Golden ratio
 // 1.618033988749894848207210029666924810953787527978420257568359... 
 public static final String VERSION = "1.6180339887";
   
 public static final String ABOUTSTRING =
         "Emulatore della Macchina Ridotta v. "+VERSION+"\n" +
         "Copyright © 2008, 2009 Claudio Imbrenda e Diego Ceccarelli,\n" +
//         "tutti i diritti riservati.\n" +
         "Questo programma è rilasciato sotto licenza\n" +
         "GNU GPL versione 3 o successiva.\n";
 
public static final int EVENT_STATE_HALTED=0;
public static final int INSTRUCTION_SET_CHANGED=1;
public static final int CONFIGURATION_SET_CHANGED=2;
public static final int CURRENT_CONFIGURATION_CHANGED=3;
public static final int EVENT_STATE_RUNNING=4;

public static final int SPEED_BEST=0;
public static final int SPEED_HISTORIC=1;
public static final int SPEED_SLOW=2;
public static final int SPEED_VERY_SLOW=3;

public static final int TELETYPEDELAY = 200;
public static final int INPUTTAPEDELAY = 3;

 /** Size of memory in cells.
 */
 public static final int MEMSIZE=1024;
 
 /** Number of opcodes.
 */
 public static final int OPCODES = 32;
 
 /** Indicates the automatic mode of operation.
  */
 public static final int AUTOMATIC_MODE=0;
 /** Indicates the manual mode of operation for instructions.
  */
 public static final int MANUAL_INSTR_MODE=1;
 /** Indicates the manual mode of operation for microinstructions.
  */
 public static final int MANUAL_MICRO_MODE=2;
 
  
 /** 18 bit mask. */
 public static final int DICIOTTO=0x3FFFF;
 /** 15 bit mask. */
 public static final short QUINDICI=0x7FFF;
 /** 10 bit mask. */
 public static final short DIECI=0x3FF;
 /** 5 bit mask. */
 public static final byte CINQUE=0x1F;
 
 
 /** Runner Thread. Performs execution when the machine is in automatic mode.
  */
 private Thread runner=null;
 
 /** Memory. Every cell is represented with an int.
 */
 private int mem[] = new int[MEMSIZE];
 /** Visualizzatore. (18 bit display).
 */
 private int VI=0;
 /** Tastiera Istruzioni. (Instruction keyboard, 15 bits).
 */
 private short TI=0;
 /** Tastiera Memoria. (Memory keyboard, 18 bits).
 */
 private int TM=0;
 
 private short curinstr=0;
 private byte curuinstr=0;
 
 /** Commutatore arresto condizionale. Switch for conditional stop.
 */
 private volatile boolean AC=false;
 
 /** Indicates if the machine is running.
 */
 private volatile boolean running=false;
 
 /** Indicates if the machine will execute external operations
 */
 private volatile boolean external=false;
 
 /** Indicates the mode of operation of the machine. SPEED_BEST runs at maximum
  * speed, SPEED_HISTORIC inserts delays to correctly emulate the true speed of
  * the Macchina Ridotta.
  */
 private volatile int speed=SPEED_BEST;
 
 /** Mode of operation.
  */
 private volatile int mode =MANUAL_INSTR_MODE;
 
 /** Adattatore di Entrata. (Input adapter).
 */
 private AdattatoreEntrata AE=null;
 
 /** Registro di Uscita. (Output register).
 */
 private List<RegistroUscita> RU=new ArrayList<RegistroUscita>();
 
 /**
  * Listeners for important state changes (e.g. halt)
  */
 private List<ActionListener> ascoltatori=new ArrayList<ActionListener>();
 
 /** Registro A
 */
 private int A;
 
 /** Registro B
 */
 private int B;
 
 /** Numeratore
 */
 private short N;
 
 /** All available configurations of the machine.
 */
 private Map<String,String[]> configs = new HashMap<String,String[]>();
 
 /**
  * Macchinina object passed to instructions.
  */
 private Macchinina macchinina;
 
 /** Name of the current confiuration.
  */
 private String currentconf;
 
 /**
  * Clock cycles count
  */
 private long clock=0;
 
 /** Current configuration. 
     Contains all and only the instructions used in the current configuration,
     indexed by opcode.
 */
 private IPair[] opcodes = new IPair[OPCODES];
 
 /**
  * Indicates whether we can compile the instruction at run-time.
  */
 private boolean weHaveJDK = true;
 
 
 /** All available instructions.
 */
 private Map<String,IPair> istruzioni = new HashMap<String,IPair>();
 
 /** Name of the confiuration described in the NI-26 technical document.
 */
 public static final String macchina1956="1956 instruction set";
 
 /** Configuration described in the NI-26 technical document.
 */ 
 private static final String[] mr_56 ={
 "NOP", "nM", "AM", "BM","A+M","B+M","A+A","A+B",
 "A-A","A-B","B+B","B-B","n+A","n+B","n-A","n-B",
 "LpA","LnA", "Vd", "Vs", "Wd", "Ws","Z+A","Z-A",
   "Z",  "F",  "G", "QM", "MQ", "EA","BU", "NOP"
 };

 /** Instruction set described in the NI-26 and NI-38 technical documents. 
     Contains arrays of 2-dimension arrays with the name of the operation
     in the first element, and the Java implementation in the second
     element.
 */ 
 private static final String[][] istr_56={
 {"NOP","return 0;"}, //NI-26..
 {"nM", "MR.writeMEM(ADDR,0);\nreturn 0;"},
 {"AM", "MR.writeMEM(ADDR,MR.readA());\nreturn 0;"},
 {"BM", "MR.writeMEM(ADDR,MR.readB());\nreturn 0;"},
 {"A+M","MR.writeMEM(ADDR,MR.readA()+MR.readMEM(ADDR));\nreturn 0;"},
 {"B+M","MR.writeMEM(ADDR,MR.readB()+MR.readMEM(ADDR));\nreturn 0;"},
 {"A+A","MR.writeA(MR.readA()+MR.readMEM(ADDR));\nreturn 0;"},
 {"A+B","MR.writeB(MR.readA()+MR.readMEM(ADDR));\nreturn 0;"}, 
 {"A-A","MR.writeA(MR.readA()-MR.readMEM(ADDR));\nreturn 0;"}, 
 {"A-B","MR.writeB(MR.readA()-MR.readMEM(ADDR));\nreturn 0;"}, 
 {"B+B","MR.writeB(MR.readB()+MR.readMEM(ADDR));\nreturn 0;"},
 {"B-B","MR.writeB(MR.readB()-MR.readMEM(ADDR));\nreturn 0;"},
 {"n+A","MR.writeA(MR.readMEM(ADDR));\nreturn 0;"},
 {"n+B","MR.writeB(MR.readMEM(ADDR));\nreturn 0;"}, 
 {"n-A","MR.writeA(-MR.readMEM(ADDR));\nreturn 0;"},
 {"n-B","MR.writeB(-MR.readMEM(ADDR));\nreturn 0;"}, 
 {"LpA","MR.writeA(MR.readA() & MR.readMEM(ADDR));\nreturn 0;"}, 
 {"LnA","MR.writeA(~MR.readMEM(ADDR));\nreturn 0;"}, 
 {"Vd", "int aa=MR.readA();\nMR.writeA((aa>>1)|(aa&0x20000));\nreturn 0;"},
 {"Vs", "MR.writeA(MR.readA()<<1);\nreturn 0;"},
 {"Wd", "int aa=MR.readA();\nMR.writeA((aa>>1)|(aa&0x20000));\nMR.writeB(((MR.readB()>>1)&0xFFFF)|(aa&0x20000)|((aa&1)<<16));\nreturn 0;"},
 {"Ws", "int aa=MR.readA();\nMR.writeA(aa<<1);\nMR.writeB((MR.readB()<<1)|((aa&0x20000)>>17));\nreturn 0;"},
 {"Z+A","if((MR.readA()&0x20000)==0)MR.writeN(ADDR);\nreturn 0;"},
 {"Z-A","if((MR.readA()&0x20000)!=0)MR.writeN(ADDR);\nreturn 0;"},
 {"Z"  ,"MR.writeN(ADDR);\nreturn 0;"},
 {"F"  ,"MR.writeN(ADDR);\nMR.setRunning(false);\nreturn 0;"},
 {"G"  ,"MR.writeN(ADDR);\nif(MR.readAC())MR.setRunning(false);\nreturn 0;"},
 {"QM" ,"MR.writeMEM(ADDR,MR.readTM());\nreturn 0;"},
 {"MQ" ,"MR.readMEM(ADDR);\nreturn 0;"},
 {"EA" ,"MR.writeA(MR.readA()+MR.readAE());\nreturn 0;"},
 {"BU" ,"MR.writeRU(MR.readB()&0x1F);\nreturn 0;"}, //END OF NI-26
 };
 private static final String[][] istr_58={
 {"LpB","MR.writeB(MR.readB()&MR.readMEM(ADDR));\nreturn 0;"},//NI-38 specific instructions..
 {"Z+B","if(MR.readB()>=0)MR.writeN(ADDR);\nreturn 0;"},
 {"Z-B","if(MR.readB()<0)MR.writeN(ADDR);\nreturn 0;"},
 {"AsM","MR.writeMEM(ADDR,(MR.readMEM(ADDR)&0x3FC00)|(MR.readA()&0x3FF));\nreturn 0;"},
 {"BsM","MR.writeMEM(ADDR,(MR.readMEM(ADDR)&0x3FC00)|(MR.readB()&0x3FF));\nreturn 0;"} //END OF NI-38
 };

 /** Extracts the opcode from an instruction.
  * @param i the instruction whose opcode is to be extracted.
  * @return the opcode part of the given instruction.
  */
 private static byte getOpcode(short i){return (byte)((i>>10)& CINQUE);}
 
 /** Extract the address part from an instruction.
  * @param i the instruction whose address part is to be extracted.
  * @return the address part of the given instruction.
  */
 // crazy JAVA! SHORT & SHORT is of type INT !
 private static short getAddress(short i){return (short)(i & DIECI);}
 
 /** Converts a binary instruction to String.
  * @param istr the value representing the instruction.
  * @return a String representation of the given instruction.
  */
 public String instrToString(short istr){
  byte opcode=getOpcode(istr);
  short addr = getAddress(istr);
  return opcodes[opcode].a + " "+addr;
 }
 
 /** Returns the opcode of the given instruction
  * @param istr the name of the instruction
  * @return the opcode of the given instruction
  * @throws IllegalArgumentException
  */
 public byte stringToOpcode(String istr)throws IllegalArgumentException{
   istr=istr.toLowerCase();
    for (byte i = 0 ; i < opcodes.length; i++)
      if ((opcodes[i]!=null)&&opcodes[i].a.equalsIgnoreCase(istr))
        return i;
    
    throw new IllegalArgumentException(
            "StringToOpcode: Unknown instruction "+istr);
  }
 
 
 /** Constructor. Instantiates the instructions. Also initializes
     the registers with random content, and loads the configuration described
     in the NI-26 technical document.
  * @param hasJDK indicates whether the presence of a JDK will be assumed.
  * If hasJDK is set to true but no JDK is actually found, the constructor will
  * use the standard workaround as if hasJDK were set to false.
 */
 public Macchina() {
  Random r = new Random();
  A=r.nextInt(DICIOTTO+1);
  B=r.nextInt(DICIOTTO+1);
  N=(short)r.nextInt(DIECI+1);
  weHaveJDK=MRUtils.hasJDK;
  

  macchinina=new MyMacchinina();
  
  if(weHaveJDK)
    for(String[] ii:istr_56)
      if(!addInstruction(ii[0],ii[1],null))
        break;
  if(weHaveJDK)
      for(String[] ii:istr_58)
        if(!addInstruction(ii[0],ii[1],null))
          break;
  if(!weHaveJDK){
            try {
                for (String[] ii : istr_56) 
                    addInstructionNOJDK(ii[0], ii[1], (Istruzione) Class.forName("macchina.istruzioni."+ii[0].replace("+","plus").replace("-", "minus")).getConstructor().newInstance());
            } 
            catch (InstantiationException ex) {ex.printStackTrace();}
            catch (IllegalAccessException ex) {ex.printStackTrace();}
            catch (IllegalArgumentException ex) {ex.printStackTrace();}
            catch (InvocationTargetException ex) {ex.printStackTrace();}
            catch (NoSuchMethodException ex) {ex.printStackTrace();}
            catch (SecurityException ex) {ex.printStackTrace();}
            catch (ClassNotFoundException ex) {ex.printStackTrace();}
            catch (ClassCastException e) {e.printStackTrace();}
                            
  }
  addConfiguration(macchina1956,mr_56);
  setConfiguration(macchina1956);
 }
 
 
 /** Returns the contents of the register A.
     @return the contents of the reister A. 
 */
 public int getA(){return A;}
 /** Sets the value of the register A.
     @param v the new value of the register A.
 */
 public void setA(int v){A=v&DICIOTTO;}
 
 /** Returns the contents of the register B.
     @return the contents of the reister B. 
 */
 public int getB(){return B;}
 /** Sets the value of the register B.
     @param v the new value of the register B.
 */
 public void setB(int v){B=v&DICIOTTO;}

 /** Returns the contents of the register N.
     @return the contents of the reister N. 
 */
 public short getN(){return N;}
 /** Sets the value of the register N.
     @param n the new value of the register N.
 */
 public void setN(short n){N=(short)(n&DIECI);}

 /** Returns the value of VI keyboard.
     @return the value of the VI keyboard.
 */
 public int getVI(){return running?0:VI;}
 
 /** Returns the value of TI keyboard.
     @return the value of the TI keyboard.
 */
 public short getTI(){return TI;}
 /** Sets TI to the given value.
     @param ti the new value of TI.
 */
 public void setTI(short ti){TI=(short)(ti&QUINDICI);}

 /** Returns the value of TM keyboard.
     @return the value of the TM keyboard.
 */
 public int getTM(){return TM;}
 
 /** Sets TM to the given value.
     @param tm the new value of TM.
 */
 public void setTM(int tm){TM=tm&DICIOTTO;}
 
 /** Returns the contents of the main menory.
     @param a the address to read.
     @return the value of the memory cell indexed by a
 */
 public int getMEM(short a){return (VI=mem[a&DIECI]);}
 
 /** Sets the contents of the main memory.
     @param a the address
     @param v the new value for the memory cell indexed by a
 */
 public void setMEM(short a,int v){mem[a&DIECI]=v&DICIOTTO;}
 
 /** Returns a copy of the memory.
  * @return an array containing a copy of the memory.
  */
 public int[] dumpMEM(){
   return mem.clone();
 }
 
 /**
  * Returns the clock cycles counter since last reset.
  * @return the number of clock cycles.
  */
 public long getClockCount(){
     return clock;
 }
 
 /**
  * Resets the clock cycles counter if the machine is not running.
  * If the machine is running does nothing.
  */
 public void resetClockCount(){
     if(!running)clock=0;
 }
 
 /**
  * Removes all non-default instructions and configurations.
  */
 private void _flushInstructions(){
     configs.clear();
     istruzioni.clear();
     
     for(String[] ii:istr_56)
         if(!addInstruction(ii[0],ii[1],null))
            System.err.println("Error inserting default instruction: "+ii[0]);
     addConfiguration(macchina1956,mr_56);
     setConfiguration(macchina1956);
     
     notifica(Macchina.CURRENT_CONFIGURATION_CHANGED);
     notifica(Macchina.CONFIGURATION_SET_CHANGED);
     notifica(Macchina.INSTRUCTION_SET_CHANGED);
 }
 
 private boolean addInstructionNOJDK(String name, String code, Istruzione inst){
     IPair istr=new IPair(code, inst);
     name=name.toLowerCase();
     istruzioni.put(name,istr);
     notifica(INSTRUCTION_SET_CHANGED);
     return true;
 }
 
 /**
  * Loads the instruction and configuration set from the given file.
  * All instructions and configurations (except the default ones)
  * will be discarded, and the current configuration will be set to the default
  * configuration.
  * The file must be in the same
  * format used by saveInstructionSet(File f). Note that default instructions 
  * and configurations will not be reloaded from the file.
  * @param f the file to load the instruction set from.
  * @return true on success, false otherwise.
  */
 public boolean loadInstructionSet(File f){
     if(!weHaveJDK)return false;
     ArrayList<Pair<String,String>> instr = new ArrayList<Pair<String,String>>();
     ArrayList<Pair<String,String[]>> confs = new ArrayList<Pair<String,String[]>>();

     try{
     BufferedReader re = new BufferedReader(new FileReader(f));
     Pair<String,String> i = new Pair<String,String>();
     Pair<String,String[]> c = new Pair<String,String[]>();
     String buf;
     
     buf=re.readLine();
     while((buf!=null)&&(!buf.equals(""))){
        i.a=buf.trim();
        if(i.a.equals("")){System.err.println("i.a.equals(\"\")");return false;}
        i.b=re.readLine();
        if(i.b.equals("")){System.err.println("i.b.equals(\"\")");return false;}
        instr.add(i);
        i= new Pair<String,String>();
        buf=re.readLine();
     }
     
     while(buf!=null){
         c.a=re.readLine();
         if(c.a==null)break;
         c.a=c.a.trim();
         c.b = new String[32];
         for(int cx=0;cx<32;cx++){
             c.b[cx]=re.readLine().trim();
             if(c.b[cx]==null){System.err.println("c.b[cx]==null");return false;}
             if(c.b[cx].equals("")){System.err.println("c.b[cx].equals(\"\")");return false;}
         }

         confs.add(c);
         c = new Pair<String,String[]>();
     }
     
     _flushInstructions();     
     for(Pair<String,String> ai:instr){
         addInstruction(ai.a, ai.b.replace("\\n", "\n"),null);
     }
     for(Pair<String,String[]> ac:confs){
         addConfiguration(ac.a, ac.b);
     }
     
     
     }catch(FileNotFoundException e){e.printStackTrace();return false;}
     catch(IOException e){e.printStackTrace();return false;}
     catch(NullPointerException e){e.printStackTrace();return false;}
     return true;
 }
 
 /**
  * Saves the current instruction and configuration set. The file format is
  * textual, and can be described as follows:<br/>
  * <i>
  * instruction name<br/>
  * instruction code (with <code>\n</code> instead of newlines so that
  * the code of the instruction fits in one line)<br/>
  * ...<br/>
  * one empty line<br/>
  * configuration name<br/>
  * 1st element of configuration<br/>
  * ..<br/>
  * 32nd element of configuration<br/>
  * ..<br/>
  * </i>
  * @param f the file to save the instructions to.
  * @return true on success, false otherwise.
  */
 public boolean saveInstructionSet(File f){
     try{
     PrintWriter out = new PrintWriter(f);
     for(Map.Entry<String,IPair> i:istruzioni.entrySet()){
         out.println(i.getKey());
         out.println(i.getValue().a.replace("\n","\\n"));
     }
     
     out.println();
     
     for(Map.Entry<String,String[]> c:configs.entrySet()){
         out.println(c.getKey());
         for(String ic:c.getValue())
             out.println(ic);
     }
     
     out.flush();
     out.close();
     
     }catch(FileNotFoundException e){
         e.printStackTrace();
         return false;
     }
     return true;
 }
 
 /** Loads a new state in memory. Memory is initialized from the given file.
  * The file must be in the same format (binary) as produced by the assembler
  * by dumpMEM(File f).
  * @param f the File to load the state from.
  * @param addr the starting address to load the dump.
  * @throws FileNotFoundException if the file is not found.
  * @throws IOException in case of I/O errors.
  */
 public void loadMemDump(File f, int addr)throws FileNotFoundException,IOException{
   FileInputStream fin = new FileInputStream(f);

   byte[] inb = new byte[4];
   int res;
   
   for(;addr<MEMSIZE;addr++){
    res=fin.read(inb);
    if(res<4)return;
    /* JAVA SUX!
       inb[0] will be cast to int and SIGN EXTENDED *BEFORE* the OR.
    */ 
    mem[addr]=(inb[0]&0xFF)|((inb[1]&0xFF)<<8)|(inb[2]<<16)&DICIOTTO;
   }
 }
 
 /**
  * Dumps the contents of memory to a file. The format used is suitable for
  * using with loadMemDump (starting at address 0).
  * @param f File to dump the memory to.
  * @throws java.io.FileNotFoundException 
  * @throws java.io.IOException
  */
 public void dumpMEM(File f)throws FileNotFoundException,IOException{
   FileOutputStream outf = new FileOutputStream(f);
   for(int cx=0;cx<mem.length;cx++)
      MRUtils.writeword(outf, mem[cx]);
   outf.close();
 }
 
 /** Loads a new state in memory. Memory is initialized from a given array, 
  * starting from the cell indicated in addr. Only the part of the array
  * fitting inside the memory will be copied.
  * @param neu the new state to load in memory. 
  * @param addr the address in memory where the state will be loaded. Only the 
  *   lower 10 bits will be considered.
  */
 public void loadMEM(int[] neu,int addr){ 
   addr=addr&DIECI;
   for(int cx=addr;cx<(Math.min(neu.length+addr,mem.length));cx++)
          mem[cx]=neu[cx];
 }
 
 /**
  * Loads a new state in memory. Equivalent to loadMEM(neu,0).
  * @param  neu the new state to load in memory. 
  */
 public void loadMEM(int[] neu){
   loadMEM(neu,0);
 }
 
 /** Reads a 5 bit character from AE. 
     @return the 5 bit character read from AE, or 0 if AE is null.
 */
 public byte readFromAE(){
     try{
         if(speed==SPEED_HISTORIC)
             Thread.sleep(INPUTTAPEDELAY);
         return (byte)(AE.read()&CINQUE);
     }catch(Exception e){}
     return 0;
 }
 
 /** Sets the input adapter.
  * @param a the new input adapter to be used.
  */
 public void setAE(AdattatoreEntrata a){AE=a;}
 
 /** Adds an output adapter to the list.
  * @param r the output adapter to add.
  */
 public void addRU(RegistroUscita r){RU.add(r);}
 
 /**
  * Adds an ActionListener for important machine state changes.
  * @param ac the ActionListener to add.
  */
 public void addActionListener(ActionListener ac){ascoltatori.add(ac);}
 
 /**
  * Removes given ActionListener.
  * @param ac the ActionListener to remove.
  */
 public void removeActionListener(ActionListener ac){ascoltatori.remove(ac);}
 
 /** Removes a given output adapter from the list.
  * @param r the output adapter to remove.
  */
 public void removeRU(RegistroUscita r){RU.remove(r);}
 
 /** Writes a 5 bit character to RU. All registered RU adapters will be notified.
     @param w the 5 bit character to be written to RU.
 */
 public void writeToRU(byte w){
     if(speed == SPEED_HISTORIC)
         try{Thread.sleep(TELETYPEDELAY);}catch(InterruptedException e){}
     
     for(RegistroUscita r:RU)
         r.write((byte)(w&CINQUE));
 }
 
 /** Adds a confiuration to the list of available configurations. A check is
     performed to verify the validity of the configuration. If a configuration
     with the same name existed, the action fails.
     @param name the name of the configuration.
     @param cc the configuration.
     @return true in case of success, false otherwise.
 */
 public boolean addConfiguration(String name,String[] cc){
  //System.err.print("Adding configuration "+name+"...");
  name=name.trim();
  if(configs.containsKey(name.toLowerCase()))return false;
  if(!verifyConfiguration(cc))return false;
  configs.put(name.toLowerCase(),(String[])cc.clone());
  notifica(CONFIGURATION_SET_CHANGED);
  //System.err.println("OK!");
  return true;
 }
 
 /** Verifies if a configuration is valid. Checks if every instruction of the
     configuration exists.
     @param cc the configuration.
     @return true if the confiuration is valid, false otherwise.
 */
 private boolean verifyConfiguration(String[] cc){
  for(String s:cc){
    //System.err.print("Verifying: "+s+"..");

    if(s!=null&&!istruzioni.containsKey(s.toLowerCase()))return false;
    //System.err.println("OK!");
  }
  return true;
 }
 
 /** Removes a confiuration.
     @param name the name of the confiuration to be removed.
     @return true in case of success, false otherwise.
 */
 public boolean removeConfiguration(String name){
   name=name.toLowerCase();
   if(name.equalsIgnoreCase(currentconf))return false;
   if(name.equalsIgnoreCase(macchina1956))return false;
   
   boolean res= (name==null)?true:configs.remove(name)!=null;
   if(res)notifica(CONFIGURATION_SET_CHANGED);
   return res;
 }
 
 /** Sets the current configuration. If the configuration exists but is
     invalid, it will be removed.
     @param name the name of the configuration.
     @return true in case of success, false otherwise.
 */
 public boolean setConfiguration(String name){ 
  if(running)return false;
  name=name.toLowerCase();
  String[] conf = configs.get(name);
  if(conf==null)return false;
  if(!verifyConfiguration(conf)){removeConfiguration(name);return false;}
  for(int cx=0;cx<OPCODES;cx++){
    String nn = conf[cx];
    if(nn==null)opcodes[cx]=null;
    else {
        nn=nn.toLowerCase();
        opcodes[cx]=new IPair(nn,istruzioni.get(nn).b);
    }
  }
  currentconf=name;
  notifica(CURRENT_CONFIGURATION_CHANGED);
  return true;
 }
 
 /**
  * Alters a given configuration.
  * @param conf the configuration to alter.
  * @param orig the index of the opcode to change.
  * @param neu  the new instruction.
  * @return true on success, false otherwise.
  */
 public boolean alterConfiguration(String conf, int orig, String neu){
   if(conf.equalsIgnoreCase(currentconf)&&running)return false;
   if(conf.equalsIgnoreCase(macchina1956))return false;
   String[] qc = configs.get(conf);
   if((conf==null)||(orig<0)||(orig>31))return false;
   neu=neu.toLowerCase();
   if(!istruzioni.containsKey(neu))return false;
   qc[orig]=neu;
   return true;
 }
 
 /** Returns a configuration. 
     @param name the name of the confiuration to be retrieved.
     @return an array of Strings containing the names of all the available
       instructions, or null if no configurations are found matching the name.
 */
 public String[] getConfiguration(String name){
   name=name.toLowerCase();
   String[] res = configs.get(name);
   if(res!=null)
     return res.clone();
     else return null;
 }
 
 /** Returns the name of the current configuration
  * @return the name of the current configuration.
  */
 public String getCurrentConfiguration(){
   return currentconf;
 }
 
 /** Returns the list of available configurations.
     @return an array of Strings containing the names of all the available
       configurations.
 */
 public String[] getConfigurations(){
  String[] res = new String[configs.size()];
  int cx=0;
  for(String n:configs.keySet())res[cx++]=n;
  
  return res;
 }
 
 /** Adds an instruction to the list of available instructions.
     No instruction will be added if name or code are null or name is empty
     or if an instruction with the same name is already present.
     @param name the name of the instruction
     @param code the Java code implementing the instruction.
     @param l a DiagnosticCollector
  * @return true in case of success, false otherwise.
 */
 public boolean addInstruction(String name,String code,List<DiagnosticCollector<JavaFileObject>> l){
   
  //System.err.print("trying to add \""+name+"\", code:\n"+code+"\n . . .");
  if(!weHaveJDK)return false; //without JDK it's pointless to even try.
  name=name.trim();
  if((name==null)||(code==null)||(name.equals("")))return false;
  name=name.toLowerCase();
  if(istruzioni.containsKey(name))return false;
  IPair istr=null;
  try{
    istr = new IPair(code,MRUtils.compileInstruction(code));
  }catch(SyntaxErrorException e){
      if(e.getMessage().equals("Java Compiler NOT found!")){
          System.err.println("Java compiler not found!\n"+
                  "Please make sure you have the full JDK installed "+
                  "if you want to add new instructions at run-time.\n");
          weHaveJDK=false;
      }
      if(l!=null)l.add(e.getDiagnostic());
  }
  
  if((istr==null)||(istr.b==null))return false;
  istruzioni.put(name,istr);
  notifica(INSTRUCTION_SET_CHANGED);
  //System.err.println("OK!");
  return true;
 }
 
 /**
  * Assigns new code to a given (existing) instruction.
  * It will fail if the instruction is
  * used by the default configuration or the machine is running.
  * @param name the name of the instruction whose code is to be altered.
  * @param code the new code to assign to the instruction.
  * @param l a DiagosticCollector
  * @return true on success, false otherwise.
  */
 public boolean alterInstruction(String name, String code,List<DiagnosticCollector<JavaFileObject>> l){
    if((!weHaveJDK) || !isAlterableInstr(name))return false;

    IPair istr=null;
    try{
    istr = new IPair(code,MRUtils.compileInstruction(code));
    }catch(SyntaxErrorException e){
      if(l!=null)l.add(e.getDiagnostic());
    }
    if((istr==null)||(istr.b==null))return false;
    if(!istruzioni.containsKey(name))return false;

    istruzioni.put(name,istr);

    return true;
 }
 
 /**
  * Indicates wheter the given instruction is removable. An instruction is
  * removable only if it isn't used by any configuration, or if it's one of
  * the default instructions. Instructions are not removable when the machine
  * is running.
  * @param ins the instruction to be tested.
  * @return true if the given instruction can be removed.
  */
 public boolean isRemovableInstr(String ins){
    if(running)return false;     
     ins=ins.toLowerCase();
     for(String[] cc:configs.values())
        for(String s:cc)
            if(ins.equalsIgnoreCase(s))
                return false;
     for(String[] q:istr_56)
         if(ins.equalsIgnoreCase(q[0]))
             return false;
     
     return true;
 }
 
 /**
  * Indicates wheter the given instruction is alterable. An instruction is
  * alterable if it isn't in the default configuration. Instructions are not 
  * alterable while the machine is running.
  * @param ins the instruction to be tested for alterability.
  * @return true id the instruction is alterable, false otherwise.
  */
 public boolean isAlterableInstr(String ins){
    if(running||!weHaveJDK)return false;
    /*
    String[] c = configs.get(macchina1955.toLowerCase());
    for(String s:c)
        if(s.equalsIgnoreCase(ins))return false;
    */
    for(String[] q:istr_56)
         if(ins.equalsIgnoreCase(q[0]))
             return false;
    
    return true;
 }
 
 /**
  * Indicates wheter the given configuration is removable. The default
  * configuration and the current configuration are not removable. No
  * configuration is removable while the machine is running.
  * @param co the configuration to be tested for removability.
  * @return true if the given configuratio is removable, false otherwise.
  */
 public boolean isRemovableConf(String co){
     return !(running||co.equalsIgnoreCase(macchina1956)||
                            co.equalsIgnoreCase(currentconf));
 }
 
 /**
  * Indicates wheter the given configuration is alterable. The default
  * configuration is not alterable. No configuration is removable while the
  * machine is running.
  * @param co
  * @return
  */
 public boolean isAlterableConf(String co){
     return !(running||co.equalsIgnoreCase(macchina1956));
 }
 
 /** Removes the given instruction from the list of available instructions.
     No instruction will be removed if it is used by a configuration.
     @param name the name of the instruction to be removed.
     @return true in case of success, false otherwise.
 */
 public boolean removeInstruction(String name){
    if(name==null)return true;
    name=name.toLowerCase();
    if(!isRemovableInstr(name))return false;
  
    boolean res=istruzioni.remove(name)!=null;
    if(res)notifica(INSTRUCTION_SET_CHANGED);
    return res;
 }

 /** Returns the names of all avaiable instructions. 
     @return an array of Strings containing the names of all the 
       available instructions.
 */
 public String[] getInstructions(){
  int cx=0;
  String[] res = new String[istruzioni.size()];
  for(String s:istruzioni.keySet())
    res[cx++]=s;
  
  return res;
 }
 
 /** Returns the Java code of the given instruction.
     @param name the name of the instruction whose code is to be retrieved.
     @return the code of the given instruction, or null, in case of error.
 */
 public String getInstructionCode(String name){
  IPair code=null;
  name=name.toLowerCase();
  code=istruzioni.get(name);
  if(code==null)return null; 
  return code.a;
 }
 
  /** Returns the instruction given the name.
     @param name the name of the instruction to be retrieved.
     @return the instruction, or null, in case of error.
 */
 public Istruzione getInstruction(String name){
  IPair code=null;
  name=name.toLowerCase();
  code=istruzioni.get(name);
  if(code==null)return null; 
  return code.b;
 }
 
 
 /**
  * Sets the execution speed. SPEED_BEST runs at maximum
  * speed, SPEED_HISTORIC inserts delays to correctly emulate the true speed of
  * the Macchina Ridotta.
  * @param nm the new speed to set.
  */
 public void setSpeed(int nm){
     if(nm<SPEED_BEST || nm> SPEED_VERY_SLOW)return;

     if(nm==SPEED_HISTORIC){
         speed_last=System.currentTimeMillis();
         speed_clock=clock;
     }
     speed=nm;    
 }
 
 /**
  * Returns the current speed mode.
  * 
  * @return the current speed mode.
  */
 public int getSpeed(){
     return speed;
 }
 
 /** Starts the machine. The action performed depends on the mode flag.
  * If mode==AUTOMATIC_MODE then automatic execution is started in a new
  * thread, else a single instruction is executed (and the machine is halted).
  * If an exception occours while executing the instructions, then the machine
  * is halted.
 */
 public synchronized void start(){
  try{
    switch(mode){
      case AUTOMATIC_MODE:
        if(!running){
          running=true;
          runner=new Thread(this);
          VI=0;        
          external=false;
          runner.start();
        }
        break;
      case MANUAL_INSTR_MODE:
        if(running){
          running=false;
          join();
          runner=null;
        }
        executeSingle();
        break;
      case MANUAL_MICRO_MODE:
        if(running){
          running=false;
          join();
          runner=null;
        }
        executeStep();
      default:
        running=false;
        break;
    }
  }catch(ScriptException e){
    e.printStackTrace();
    running=false;
  }
 }

 /** Executes a single instruction. The instruction is fetched from memory using
  * fetch(), and then is executed. If the machine is not running and the 
  * external operation switch is enabled, then the instruction excuted is taken
  * from TI.
  * @throws ScriptException if there are errors executing the script.
 */ 
 private void executeSingle() throws ScriptException{
  int opcode=0;
  short addr=0;
  if(curuinstr==0){
    if((!running)&&external)
        curinstr=TI;
    else{
        curinstr=fetch();
        clock++;
    }
    curuinstr=1;
  }
  
  opcode=getOpcode(curinstr);
  addr=getAddress(curinstr);
  
  try{
    while (curuinstr!=0){
        curuinstr=opcodes[opcode].b.execute(addr, macchinina, curuinstr,null);
        clock++;
    }
  }catch(NullPointerException ignore){
      curuinstr=0;
      clock++;
  }
  catch(Exception e){
    e.printStackTrace();
    throw new ScriptException(e);
  }
 }
 
 /**
  * Executes a single microinstruction.
  */
 private void executeStep(){
  if(curuinstr==0){
    if((!running)&&external)
      curinstr=TI;
    else {
      curinstr=fetch();
      clock++;
    }
    curuinstr=1;
  }
  else{
    try{
        curuinstr=opcodes[getOpcode(curinstr)].b
            .execute(getAddress(curinstr), macchinina, curuinstr,null);
    }catch(NullPointerException ignore){
        curuinstr=0;
    }
    clock++;
  }
 }
 
 /** Returns the mode of operation.
     @return the value of the microistr flag.
 */
 public int getMode(){return mode;}
 /** Sets the mode of operation to the given value.
     @param v the new mode of operation.
 */
 public void setMode(int v){mode=(v&3);}

 /** Returns the value of AC flag.
     @return the value of the AC flag.
 */
 public boolean getAC(){return AC;}
 /** Sets the AC flag to the given value.
     @param v the new value of the AC flag.
 */
 public void setAC(boolean v){AC=v;}

 /** Returns the value of external flag.
     @return the value of the external flag.
 */
 public boolean getExternal(){return external;}
 /** Sets the external flag to the given value.
     @param v the new value of the external flag.
 */
 public void setExternal(boolean v){external=v;}
 
 /** Waits for the machine to halt.
  */
 public synchronized void join(){
   if(runner==null)return;
   while(true)try{
     runner.join();
     runner=null;
     return;
   }catch(InterruptedException e){}
 }
 
 /** Returns the value of the running flag.
     @return the value of the running flag.
 */
 public boolean isRunning(){return running;}
 
 /** Fetches an instruction from memory. 
     The address is indicated by N. 
     N is incremented after the fetch.
     @return the fetched instruction
 */
 private short fetch(){
  short res= (short)(getMEM(N)&QUINDICI);
  N=(short)((N+1)&DIECI);
  return res;
 }
  
 
 private long speed_last=0;
 private long speed_clock=0;
 /**
  * Delays execution when needed to accurately emulate the true speed of the 
  * Macchina Ridotta.
  */
 private void maybeDelay(){
     switch(speed){
        case SPEED_BEST: 
            return;
        case SPEED_SLOW:
            try{Thread.sleep(50);}catch(InterruptedException e){}
            break;
        case SPEED_VERY_SLOW:
            try{Thread.sleep(1000);}catch(InterruptedException e){}
            break;
        case SPEED_HISTORIC:
            long ct = System.currentTimeMillis()-speed_last;
            long cl = clock - speed_clock;

            if(ct==0)return;

            if((cl/ct)>83)
                try{Thread.sleep(10);}catch(InterruptedException e){}
            break;
        default:
    }
 }
 
 /** Starts automatic execution until running becomes false.
 */
 public void run(){
    notifica(EVENT_STATE_RUNNING);
    setSpeed(speed);
    try{
        while(running){
            executeSingle();
            maybeDelay();
        }
    }catch(ScriptException e){
        running=false;
    }
    notifica(EVENT_STATE_HALTED);
 }
 
 /**
  * Notifies all listeners with the given action code.
  * @param codice the code to notify.
  */
 private void notifica(int codice){
      for(ActionListener al:ascoltatori)
     al.actionPerformed(new ActionEvent(this, codice, ""));
 }
 
 /**
  * Private class wrapping macchina so that only a subset of Macchina is usable
  * from the Javascript context.
  */
 private class MyMacchinina implements Macchinina{
   
   /**
    * Public constructor. Does nothing.
    */
   public MyMacchinina(){}
 
   /**
    * Wrapper for getTM().
    * @return getTM().
    */
   public int readTM(){return getTM();}
   
   /**
    * Wrapper for getTI().
    * @return getTI().
    */
   public int readTI(){return getTI();}
   
   /**
    * Wrapper for getA().
    * @return getA().
    */
   public int readA(){return getA();}
   /**
    * Wrapper for getB().
    * @return getB().
    */
   public int readB(){return getB();}
   /**
    * Wrapper for getN().
    * @return getN().
    */
   public int readN(){return getN();}
   
   /**
    * Wrapper for setA().
    * @param v same as setA(v).
    */
   public void writeA(int v){setA(v);}
   /**
    * Wrapper for setB().
    * @param v same as setB(v).
    */   
   public void writeB(int v){setB(v);}
    /**
    * Wrapper for setN().
    * @param v same as setN(v).
    */ 
   public void writeN(short v){setN(v);}
    /**
    * Wrapper for setN().
    * @param v same as setN(v).
    */ 
   public void writeN(int v){setN((short)v);}
      
   /**
    * Wrapper for readFromAE().
    * @return readFromAE().
    */
   public byte readAE(){return readFromAE();}
   /**
    * Wrapper for writeToRU(b).
    * @param b same as writeToRU(b).
    */
   public void writeRU(byte b){writeToRU(b);}
   /**
    * Wrapper for writeToRU(b).
    * @param b same as writeToRU(b).
    */
   public void writeRU(int b){writeToRU((byte)b);}
      
    /**
    * Wrapper for getAC().
    * @return getAC().
    */
   public boolean readAC(){return getAC();}
   
   /**
    * Wrapper for getMEM(a).
    * @param a same as getMEM(a).
    * @return getMEM(a).
    */
   public int readMEM(short a){return getMEM(a);}
   /**
    * Wrapper for setMEM(a,v).
    * @param a same as setMEM(a,v).
    * @param v same as setMEM(a,v).
    */
   public void writeMEM(short a,int v){setMEM(a, v);}
   
   /**
    * Sets the running flag to the given value.
    * @param b the value to set running to.
    */
   public void setRunning(boolean b){running=b;}
   
 }
 
}

